IDiginsightActivitiesOptions Interface
Configuration for activity source registration and listener setup
IDiginsightActivitiesOptions Interface
The IDiginsightActivitiesOptions provides configuration for activity source registration controlling which ActivitySource instances are monitored by Diginsight diagnostics.
In particular, it maintains a dictionary of activity source name patterns mapped to boolean values indicating whether activities from matching sources should be captured. This interface enables selective activity listening, allowing applications to focus on relevant activity sources while ignoring others.
IDiginsightActivitiesOptions is part of Diginsight.Diagnostics (Diginsight.Diagnostics.dll).
The primary implementation is DiginsightActivitiesOptions, which also implements several other interfaces for comprehensive activity configuration including logging, metrics, and dynamic configuration support.
Table of Contents
π Overview
The IDiginsightActivitiesOptions interface controls which ActivitySource instances Diginsight monitors:
- Pattern-based registration: Use wildcard patterns to match activity source names
- Inclusion/exclusion control: Boolean values determine whether to listen to matching sources
- Multiple pattern support: Configure multiple patterns with different rules
- Dynamic discovery: Automatically applies to new activity sources created at runtime
- Integration point: Used by
IActivityListenerRegistrationimplementations to determine listening behavior
This interface is consumed by several Diginsight components: - ActivityLifecycleLogEmitterRegistration: Determines which activities to log - SpanDurationMetricRecorderRegistration: Determines which activities to measure - Custom IActivityListenerRegistration implementations: Can use this for consistent filtering
Key Features
- Flexible Pattern Matching: Support for exact names, prefix wildcards (
*Suffix), suffix wildcards (Prefix*), and infix wildcards (Prefix*Suffix) - Boolean Control: Map patterns to
true(include) orfalse(exclude) for fine-grained control - Multiple Pattern Evaluation: When multiple patterns match, ALL must agree for the source to be included
- Case-Insensitive Matching: Activity source name comparison is case-insensitive
- Runtime Application: Patterns evaluated when new
ActivitySourceinstances are created - Zero-Configuration Option: Empty dictionary means no activity sources are listened to by default
- Complementary to IDiginsightActivitiesLogOptions: This controls which sources to listen to;
IDiginsightActivitiesLogOptionscontrols how to log them
Activity Source Name Patterns
Activity sources are identified by names (e.g., "MyApp.Services", "System.Net.Http"). The ActivitySources dictionary maps patterns to boolean values:
| Pattern | Matches | Example Sources |
|---|---|---|
"MyApp" |
Exact name only | MyApp |
"MyApp.*" |
Names starting with prefix | MyApp.Services, MyApp.Data.Repositories |
"*.Controllers" |
Names ending with suffix | WebApi.Controllers, Admin.Controllers |
"MyApp.*.Service" |
Names with prefix and suffix | MyApp.Users.Service, MyApp.Orders.Service |
"*" |
All activity sources | Everything |
Boolean Values: - true: Include activities from matching sources - false: Exclude activities from matching sources (explicit denial)
Registration Behavior
The listening decision follows this logic:
// Pseudo-code for ShouldListenTo logic
bool ShouldListenTo(ActivitySource source)
{
// Find all patterns that match the source name
var matchingPatterns = ActivitySources
.Where(kvp => PatternMatches(source.Name, kvp.Key))
.Select(kvp => kvp.Value);
// If no patterns match, don't listen
if (!matchingPatterns.Any())
return false;
// All matching patterns must be true to listen
return matchingPatterns.All(value => value == true);
}Key Behaviors: - No matches: Source is NOT listened to (opt-in by default) - All matches are true: Source IS listened to - Any match is false: Source is NOT listened to (explicit exclusion wins) - Mixed true/false: Source is NOT listened to (requires unanimous agreement)
π Additional Details
Pattern Matching Rules
Pattern matching is implemented by ActivityUtils.NameMatchesPattern with the following rules:
1. Exact Match (no wildcards):
2. Suffix Wildcard:
3. Prefix Wildcard:
4. Infix Wildcard:
5. Match All:
Invalid Patterns:
Case Sensitivity: - All comparisons are case-insensitive using StringComparison.OrdinalIgnoreCase - "MyApp.*" matches "myapp.services", "MYAPP.DATA", "MyApp.Controllers"
Boolean Logic for Multiple Matches
When an activity source name matches multiple patterns, the boolean values determine the final decision:
Scenario 1: All True (Include)
Scenario 2: Mixed True/False (Exclude)
Scenario 3: All False (Explicit Exclusion)
Use Cases: - Include broad, exclude specific: ["*"] = true + ["System.*"] = false - Include specific, exclude subset: ["MyApp.*"] = true + ["MyApp.Internal.*"] = false - Explicit allow-listing: Only include patterns with true, no catch-all
Integration with ActivityListener
The IDiginsightActivitiesOptions is consumed by IActivityListenerRegistration implementations:
// Simplified from ActivityLifecycleLogEmitterRegistration
public class ActivityLifecycleLogEmitterRegistration : IActivityListenerRegistration
{
private readonly IDiginsightActivitiesOptions activitiesOptions;
public bool ShouldListenTo(ActivitySource activitySource)
{
string activitySourceName = activitySource.Name;
// Find all matching patterns
IEnumerable<bool> matches = activitiesOptions.ActivitySources
.Where(kvp => ActivityUtils.NameMatchesPattern(activitySourceName, kvp.Key))
.Select(kvp => kvp.Value)
.ToArray();
// Listen only if:
// 1. At least one pattern matches
// 2. ALL matching patterns are true
return matches.Any() && matches.All(value => value);
}
}Integration Flow: 1. Application creates ActivitySource with name (e.g., "MyApp.Services") 2. .NET runtime calls ActivityListener.ShouldListenTo for each registered listener 3. Diginsightβs IActivityListenerRegistration implementations call ShouldListenTo 4. Implementation queries IDiginsightActivitiesOptions.ActivitySources 5. Pattern matching determines if the source should be listened to 6. If yes, activities from that source will be captured and processed
ActivitySource Discovery
Activity sources can be registered at any time during application lifecycle:
Static Registration (Common):
Dynamic Registration (Runtime):
Key Points: - Diginsight patterns apply to ALL activity sources, regardless of when theyβre created - No need to pre-register activity sources with Diginsight - ShouldListenTo is called lazily when activities are started - Patterns are evaluated each time (supports dynamic configuration changes)
βοΈ Configuration
Configuration in appsettings.json
Configuration Properties: - ActivitySources: Dictionary mapping activity source name patterns to boolean values - Key: Pattern string (supports exact, prefix *, suffix *, and infix wildcards) - Value: true to include, false to exclude
Common Patterns:
{
"ActivitySources": {
// Include all application sources
"MyApp.*": true,
// Include specific third-party sources
"System.Net.Http": true,
"Npgsql": true,
"Microsoft.EntityFrameworkCore": true,
// Include ASP.NET Core diagnostics
"Microsoft.AspNetCore.*": true,
// Exclude system internals
"System.Private.*": false,
"System.Diagnostics.Eventing.*": false,
// Catch-all: include everything not explicitly excluded
"*": true
}
}Configuration in the startup sequence
Register Diginsight diagnostics and configure activity sources:
// In Program.cs
using Diginsight.Diagnostics;
var builder = WebApplication.CreateBuilder(args);
// Add Diginsight diagnostics
builder.Services.AddDiginsightDiagnostics();
// Configure activity sources
builder.Services.Configure<DiginsightActivitiesOptions>(options =>
{
// Include application activity sources
options.ActivitySources["MyApp.*"] = true;
// Include HTTP client activities
options.ActivitySources["System.Net.Http"] = true;
// Include ASP.NET Core activities
options.ActivitySources["Microsoft.AspNetCore.*"] = true;
// Exclude high-frequency internal diagnostics
options.ActivitySources["System.Diagnostics.*"] = false;
});
// Or bind from configuration
builder.Services.Configure<DiginsightActivitiesOptions>(
builder.Configuration.GetSection("Diginsight:DiginsightActivitiesOptions"));
var app = builder.Build();Fluent Configuration Helper:
public static class ActivitySourceConfiguration
{
public static IServiceCollection AddApplicationActivitySources(
this IServiceCollection services)
{
return services.Configure<DiginsightActivitiesOptions>(options =>
{
// Include application sources
options.ActivitySources["MyApp.*"] = true;
// Include common infrastructure
options.ActivitySources["System.Net.Http"] = true;
options.ActivitySources["Microsoft.EntityFrameworkCore"] = true;
options.ActivitySources["Npgsql"] = true;
// Include ASP.NET Core
options.ActivitySources["Microsoft.AspNetCore.Hosting"] = true;
options.ActivitySources["Microsoft.AspNetCore.Routing"] = true;
// Exclude noisy sources
options.ActivitySources["System.Private.*"] = false;
});
}
}
// Usage
builder.Services
.AddDiginsightDiagnostics()
.AddApplicationActivitySources();Configuring Multiple Activity Sources
Organize patterns by category for maintainability:
services.Configure<DiginsightActivitiesOptions>(options =>
{
// Application layer
AddApplicationSources(options);
// Infrastructure layer
AddInfrastructureSources(options);
// Third-party integrations
AddThirdPartySources(options);
// Exclusions
AddExclusions(options);
});
static void AddApplicationSources(DiginsightActivitiesOptions options)
{
options.ActivitySources["MyApp.Controllers.*"] = true;
options.ActivitySources["MyApp.Services.*"] = true;
options.ActivitySources["MyApp.Repositories.*"] = true;
}
static void AddInfrastructureSources(DiginsightActivitiesOptions options)
{
options.ActivitySources["System.Net.Http"] = true;
options.ActivitySources["Microsoft.AspNetCore.*"] = true;
}
static void AddThirdPartySources(DiginsightActivitiesOptions options)
{
options.ActivitySources["Npgsql"] = true;
options.ActivitySources["StackExchange.Redis"] = true;
options.ActivitySources["Azure.*"] = true;
}
static void AddExclusions(DiginsightActivitiesOptions options)
{
options.ActivitySources["System.Private.*"] = false;
options.ActivitySources["System.Diagnostics.Eventing.*"] = false;
options.ActivitySources["MyApp.HighFrequency.*"] = false;
}Dynamic Configuration Updates
Update activity sources at runtime using IOptionsMonitor:
public class DiagnosticsController : ControllerBase
{
private readonly IOptionsMonitor<DiginsightActivitiesOptions> _optionsMonitor;
public DiagnosticsController(IOptionsMonitor<DiginsightActivitiesOptions> optionsMonitor)
{
_optionsMonitor = optionsMonitor;
}
[HttpPost("enable-source/{sourceName}")]
public IActionResult EnableActivitySource(string sourceName)
{
var options = _optionsMonitor.CurrentValue;
// Add or update pattern
options.ActivitySources[sourceName] = true;
return Ok($"Enabled activity source: {sourceName}");
}
[HttpPost("disable-source/{sourceName}")]
public IActionResult DisableActivitySource(string sourceName)
{
var options = _optionsMonitor.CurrentValue;
// Explicitly exclude
options.ActivitySources[sourceName] = false;
return Ok($"Disabled activity source: {sourceName}");
}
[HttpGet("active-sources")]
public IActionResult GetActiveSources()
{
var options = (IDiginsightActivitiesOptions)_optionsMonitor.CurrentValue;
return Ok(new
{
patterns = options.ActivitySources.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value ? "included" : "excluded")
});
}
}π‘ Usage Examples
Basic Usage
using Diginsight.Diagnostics;
using Microsoft.Extensions.Options;
using System.Diagnostics;
public class ActivitySourceMonitor
{
private readonly IOptionsMonitor<DiginsightActivitiesOptions> _optionsMonitor;
private readonly ILogger<ActivitySourceMonitor> _logger;
public ActivitySourceMonitor(
IOptionsMonitor<DiginsightActivitiesOptions> optionsMonitor,
ILogger<ActivitySourceMonitor> logger)
{
_optionsMonitor = optionsMonitor;
_logger = logger;
}
public void CheckSourceConfiguration(ActivitySource source)
{
var options = (IDiginsightActivitiesOptions)_optionsMonitor.CurrentValue;
_logger.LogInformation("Checking configuration for source: {SourceName}", source.Name);
// Find matching patterns
var matchingPatterns = options.ActivitySources
.Where(kvp => ActivityUtils.NameMatchesPattern(source.Name, kvp.Key))
.ToList();
if (!matchingPatterns.Any())
{
_logger.LogInformation(" No patterns match - source will NOT be listened to");
}
else
{
foreach (var kvp in matchingPatterns)
{
_logger.LogInformation(" Pattern '{Pattern}': {Action}",
kvp.Key,
kvp.Value ? "INCLUDE" : "EXCLUDE");
}
bool shouldListen = matchingPatterns.All(kvp => kvp.Value);
_logger.LogInformation(" Final decision: {Decision}",
shouldListen ? "LISTEN" : "DO NOT LISTEN");
}
}
}Explanation: - Access current options via IOptionsMonitor<DiginsightActivitiesOptions> - Cast to IDiginsightActivitiesOptions to access the interface properties - Use ActivityUtils.NameMatchesPattern for pattern matching - All matching patterns must be true for listening to occur
Pattern-Based Filtering
// Scenario: Monitor only application code, not framework internals
services.Configure<DiginsightActivitiesOptions>(options =>
{
// Include application namespace
options.ActivitySources["MyCompany.MyApp.*"] = true;
// Include specific framework components
options.ActivitySources["Microsoft.AspNetCore.Hosting"] = true;
options.ActivitySources["Microsoft.AspNetCore.Routing"] = true;
options.ActivitySources["System.Net.Http"] = true;
// Exclude everything else by not adding catch-all "*" = true
});
// Result:
// β Listened: MyCompany.MyApp.Services.UserService
// β Listened: MyCompany.MyApp.Controllers.HomeController
// β Listened: Microsoft.AspNetCore.Hosting
// β Listened: System.Net.Http
// β Ignored: System.Diagnostics.DiagnosticSource
// β Ignored: System.Private.CoreLib
// β Ignored: Microsoft.Extensions.LoggingAlternative: Include All, Exclude Specific:
services.Configure<DiginsightActivitiesOptions>(options =>
{
// Include everything
options.ActivitySources["*"] = true;
// Explicitly exclude noisy or internal sources
options.ActivitySources["System.Private.*"] = false;
options.ActivitySources["System.Diagnostics.*"] = false;
options.ActivitySources["Microsoft.Extensions.*"] = false;
});
// Result:
// β Listened: MyCompany.MyApp.Services.UserService
// β Listened: Microsoft.AspNetCore.Hosting
// β Listened: System.Net.Http
// β Ignored: System.Private.CoreLib (explicit exclusion)
// β Ignored: System.Diagnostics.DiagnosticSource (explicit exclusion)
// β Ignored: Microsoft.Extensions.Logging (explicit exclusion)Excluding Specific Sources
// Scenario: Include application but exclude high-frequency subsystem
services.Configure<DiginsightActivitiesOptions>(options =>
{
// Include entire application
options.ActivitySources["MyApp.*"] = true;
// Exclude high-frequency polling subsystem
options.ActivitySources["MyApp.Polling.*"] = false;
// Exclude cache operations
options.ActivitySources["MyApp.*.Cache"] = false;
});
// Testing which sources are listened to:
var sources = new[]
{
new ActivitySource("MyApp.Services.UserService"), // β Included (matches MyApp.*)
new ActivitySource("MyApp.Controllers.HomeController"), // β Included (matches MyApp.*)
new ActivitySource("MyApp.Polling.HealthCheck"), // β Excluded (explicit false)
new ActivitySource("MyApp.Services.Cache"), // β Excluded (matches *.Cache)
new ActivitySource("ThirdParty.Library"), // β Not matched (no pattern)
};Example with Multiple Exclusions:
services.Configure<DiginsightActivitiesOptions>(options =>
{
// Broad inclusion
options.ActivitySources["*"] = true;
// Multiple specific exclusions
options.ActivitySources["*.HealthCheck"] = false; // All health checks
options.ActivitySources["*.Metrics"] = false; // All metrics collectors
options.ActivitySources["System.*"] = false; // All system sources
options.ActivitySources["Microsoft.Extensions.*"] = false; // Framework internals
});Custom Activity Listener Registration
Implement IActivityListenerRegistration using IDiginsightActivitiesOptions:
public class CustomActivityProcessor : IActivityListenerRegistration
{
private readonly IDiginsightActivitiesOptions _activitiesOptions;
private readonly ILogger<CustomActivityProcessor> _logger;
public IActivityListenerLogic Logic { get; }
public CustomActivityProcessor(
IOptions<DiginsightActivitiesOptions> activitiesOptions,
ILogger<CustomActivityProcessor> logger)
{
_activitiesOptions = activitiesOptions.Value.Freeze();
_logger = logger;
Logic = new CustomActivityListenerLogic(logger);
}
public bool ShouldListenTo(ActivitySource activitySource)
{
string sourceName = activitySource.Name;
// Use same pattern matching as built-in registrations
IEnumerable<bool> matches = _activitiesOptions.ActivitySources
.Where(kvp => ActivityUtils.NameMatchesPattern(sourceName, kvp.Key))
.Select(kvp => kvp.Value)
.ToArray();
bool shouldListen = matches.Any() && matches.All(v => v);
_logger.LogDebug(
"Activity source '{SourceName}': {Decision}",
sourceName,
shouldListen ? "LISTENING" : "IGNORING");
return shouldListen;
}
}
// Register the custom listener
services.AddSingleton<IActivityListenerRegistration, CustomActivityProcessor>();π Advanced Usage
Wildcard Pattern Combinations
Combine different pattern types for complex filtering:
public static class AdvancedActivitySourceConfiguration
{
public static IServiceCollection ConfigureAdvancedActivitySources(
this IServiceCollection services)
{
return services.Configure<DiginsightActivitiesOptions>(options =>
{
// Strategy: Layered filtering with explicit rules
// Layer 1: Catch-all include
options.ActivitySources["*"] = true;
// Layer 2: Exclude system internals (prefix pattern)
options.ActivitySources["System.Private.*"] = false;
options.ActivitySources["System.Diagnostics.Eventing.*"] = false;
// Layer 3: Exclude high-frequency patterns (suffix pattern)
options.ActivitySources["*.HealthCheck"] = false;
options.ActivitySources["*.HeartBeat"] = false;
options.ActivitySources["*.Polling"] = false;
// Layer 4: Exclude specific patterns (infix pattern)
options.ActivitySources["MyApp.*.Cache"] = false;
options.ActivitySources["MyApp.*.Metrics"] = false;
// Layer 5: Re-include important caches (more specific pattern)
options.ActivitySources["MyApp.Core.Cache"] = true;
// Note: This WON'T work if MyApp.Core.Cache also matches MyApp.*.Cache
// because all matching patterns must be true!
});
}
}Important Pattern Interaction:
// Configuration
options.ActivitySources["MyApp.*"] = true;
options.ActivitySources["*.Cache"] = false;
// Test: MyApp.Core.Cache
// Matches: ["MyApp.*" = true, "*.Cache" = false]
// Result: NOT listened to (requires ALL matches to be true)
// Solution: Be more specific with exclusions
options.ActivitySources["MyApp.Services.*.Cache"] = false; // Exclude service caches
// MyApp.Core.Cache now only matches "MyApp.*" = true β Listened to!Hierarchical Source Filtering
Organize activity sources hierarchically and filter by level:
// Application structure:
// MyApp (root)
// MyApp.Api (presentation)
// MyApp.Api.Controllers (controllers)
// MyApp.Services (business logic)
// MyApp.Services.Users (user service)
// MyApp.Data (data access)
// MyApp.Data.Repositories (repositories)
// MyApp.Infrastructure (infrastructure)
services.Configure<DiginsightActivitiesOptions>(options =>
{
// Scenario 1: Monitor API and Services only
options.ActivitySources["MyApp.Api.*"] = true;
options.ActivitySources["MyApp.Services.*"] = true;
// Data and Infrastructure are not included (no matching patterns)
// Scenario 2: Monitor everything except infrastructure internals
options.ActivitySources["MyApp.*"] = true;
options.ActivitySources["MyApp.Infrastructure.Internal.*"] = false;
// Scenario 3: Monitor specific service and its dependencies
options.ActivitySources["MyApp.Services.Users.*"] = true;
options.ActivitySources["MyApp.Data.Repositories.UserRepository"] = true;
});Environment-Specific Hierarchical Filtering:
public static IServiceCollection ConfigureEnvironmentSpecificSources(
this IServiceCollection services,
IWebHostEnvironment environment)
{
return services.Configure<DiginsightActivitiesOptions>(options =>
{
if (environment.IsDevelopment())
{
// Development: Monitor everything
options.ActivitySources["*"] = true;
}
else if (environment.IsStaging())
{
// Staging: Monitor app and key infrastructure
options.ActivitySources["MyApp.*"] = true;
options.ActivitySources["System.Net.Http"] = true;
options.ActivitySources["Microsoft.EntityFrameworkCore"] = true;
}
else // Production
{
// Production: Monitor business logic only
options.ActivitySources["MyApp.Api.*"] = true;
options.ActivitySources["MyApp.Services.*"] = true;
// Exclude high-frequency operations
options.ActivitySources["MyApp.*.Cache"] = false;
options.ActivitySources["MyApp.*.HealthCheck"] = false;
}
});
}Integration with Custom Listeners
Create specialized listeners that use consistent filtering:
// Custom listener for security auditing
public class SecurityAuditListener : IActivityListenerRegistration
{
private readonly IDiginsightActivitiesOptions _activitiesOptions;
private readonly IAuditLogger _auditLogger;
public IActivityListenerLogic Logic { get; }
public SecurityAuditListener(
IOptions<DiginsightActivitiesOptions> activitiesOptions,
IAuditLogger auditLogger)
{
_activitiesOptions = activitiesOptions.Value.Freeze();
_auditLogger = auditLogger;
Logic = new SecurityAuditLogic(auditLogger);
}
public bool ShouldListenTo(ActivitySource activitySource)
{
// First check: Use standard Diginsight filtering
string sourceName = activitySource.Name;
IEnumerable<bool> matches = _activitiesOptions.ActivitySources
.Where(kvp => ActivityUtils.NameMatchesPattern(sourceName, kvp.Key))
.Select(kvp => kvp.Value)
.ToArray();
if (!matches.Any() || !matches.All(v => v))
return false; // Respect Diginsight configuration
// Second check: Only audit security-related sources
return sourceName.Contains("Auth", StringComparison.OrdinalIgnoreCase) ||
sourceName.Contains("Security", StringComparison.OrdinalIgnoreCase) ||
sourceName.Contains("Identity", StringComparison.OrdinalIgnoreCase);
}
}
// Custom listener for performance monitoring
public class PerformanceMonitorListener : IActivityListenerRegistration
{
private readonly IDiginsightActivitiesOptions _activitiesOptions;
public IActivityListenerLogic Logic { get; }
public PerformanceMonitorListener(
IOptions<DiginsightActivitiesOptions> activitiesOptions)
{
_activitiesOptions = activitiesOptions.Value.Freeze();
Logic = new PerformanceMonitorLogic();
}
public bool ShouldListenTo(ActivitySource activitySource)
{
// Use Diginsight filtering but only for services and API layers
string sourceName = activitySource.Name;
if (!sourceName.Contains(".Services.", StringComparison.OrdinalIgnoreCase) &&
!sourceName.Contains(".Api.", StringComparison.OrdinalIgnoreCase))
{
return false; // Only monitor services and API
}
// Apply standard pattern matching
IEnumerable<bool> matches = _activitiesOptions.ActivitySources
.Where(kvp => ActivityUtils.NameMatchesPattern(sourceName, kvp.Key))
.Select(kvp => kvp.Value)
.ToArray();
return matches.Any() && matches.All(v => v);
}
}π§ Troubleshooting
Common Issues
1. Activities Not Being Captured
Activities may not appear due to activity source not being included.
// Problem: Created ActivitySource but activities not captured
var source = new ActivitySource("MyApp.NewFeature");
// Check 1: Verify source is in configuration
var options = _optionsMonitor.CurrentValue;
_logger.LogInformation("Configured patterns: {Patterns}",
string.Join(", ", ((IDiginsightActivitiesOptions)options).ActivitySources.Keys));
// Check 2: Test pattern matching
bool matches = options.ActivitySources.Any(kvp =>
ActivityUtils.NameMatchesPattern("MyApp.NewFeature", kvp.Key));
_logger.LogInformation("Source name matches any pattern: {Matches}", matches);
// Solution 1: Add explicit pattern
options.ActivitySources["MyApp.NewFeature"] = true;
// Solution 2: Use broader pattern
options.ActivitySources["MyApp.*"] = true;2. Pattern Not Matching Expected Sources
Pattern may be too specific or using incorrect wildcard placement.
Ensure that: - Pattern uses case-insensitive matching (no need to worry about case) - Wildcard is at beginning, end, or both (not middle segments) - Pattern doesnβt have multiple wildcards (only supports 0, 1, or 2 segments)
Debugging Patterns:
public class PatternDebugger
{
public void TestPattern(string sourceName, string pattern)
{
bool matches = ActivityUtils.NameMatchesPattern(sourceName, pattern);
Console.WriteLine($"Pattern '{pattern}' vs Source '{sourceName}': {matches}");
}
public void TestAllPatterns(string sourceName)
{
var options = (IDiginsightActivitiesOptions)_optionsMonitor.CurrentValue;
foreach (var kvp in options.ActivitySources)
{
bool matches = ActivityUtils.NameMatchesPattern(sourceName, kvp.Key);
Console.WriteLine($" Pattern '{kvp.Key}': {(matches ? "β MATCH" : "β NO MATCH")} " +
$"(Action: {(kvp.Value ? "INCLUDE" : "EXCLUDE")})");
}
}
}
// Usage
debugger.TestAllPatterns("MyApp.Services.UserService");
// Output:
// Pattern 'MyApp.*': β MATCH (Action: INCLUDE)
// Pattern '*.Services.*': β MATCH (Action: INCLUDE)
// Pattern 'System.*': β NO MATCH (Action: EXCLUDE)3. Exclusion Not Working
Exclusion (false value) may be overridden by other patterns.
- Symptom: Source is still listened to despite
falsepattern - Cause: Another pattern matches with
truevalue, but exclusion requires ALL matches to betrue - Wait, thatβs backwards! Let me re-read the logicβ¦
Actually, looking at the code again:
This means: βListen if thereβs at least one match AND all matches are trueβ
So if ANY pattern matches with false, the source is NOT listened to. Let me correct the issue:
- Symptom: Source is NOT being listened to despite
truepatterns - Cause: Another pattern also matches with
falsevalue - Solution: Make exclusion patterns more specific or remove them
// Problem configuration
options.ActivitySources["MyApp.*"] = true; // Matches MyApp.Services
options.ActivitySources["*.Services"] = false; // Also matches MyApp.Services
// Result: MyApp.Services is NOT listened to (exclusion wins)
// Solution 1: More specific exclusion
options.ActivitySources["MyApp.*"] = true;
options.ActivitySources["ThirdParty.*.Services"] = false; // Only exclude third-party
// Result: MyApp.Services IS listened to
// Solution 2: More specific inclusion
options.ActivitySources["MyApp.Services"] = true; // Exact match
options.ActivitySources["*.Services"] = false; // Exclude other services
// Wait, this won't work either! MyApp.Services matches BOTH patterns
// Result: Still NOT listened to (exclusion wins)
// Actual Solution: Remove conflicting pattern
options.ActivitySources["MyApp.*"] = true;
// Don't add "*.Services" = false
// Result: MyApp.Services IS listened to4. Changes Not Taking Effect
Configuration updates may not reflect immediately for existing activity sources.
- Symptom: Updated
ActivitySourcesdictionary but behavior unchanged - Cause: Activity listeners are registered once at startup
- Solution: Restart application or use hot-reload mechanisms
// Note: ShouldListenTo is called when ActivitySource is first used
// Existing ActivitySource instances may have cached listener state
// New ActivitySource instances will use updated configuration
// To force re-evaluation: Create new ActivitySource instance
var oldSource = new ActivitySource("MyApp.Services"); // Uses old config
// ... update configuration ...
var newSource = new ActivitySource("MyApp.Services"); // Uses new configDebugging
Enable detailed logging to troubleshoot activity source registration:
// In appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Diginsight.Diagnostics": "Debug",
"System.Diagnostics": "Debug" // .NET activity system logs
}
}
}
// Or in code
services.Configure<LoggerFilterOptions>(options =>
{
options.AddFilter("Diginsight.Diagnostics", LogLevel.Debug);
options.AddFilter("System.Diagnostics", LogLevel.Debug);
});Debugging Techniques: - Log all configured patterns at startup - Log ShouldListenTo decisions for each activity source - Test pattern matching with known source names - Verify activity source name matches expected value - Check that activity sources are actually creating activities
Diagnostic Endpoint:
public class ActivitySourceDiagnosticsController : ControllerBase
{
private readonly IOptionsMonitor<DiginsightActivitiesOptions> _optionsMonitor;
[HttpGet("/_diagnostics/activity-sources")]
public IActionResult GetActivitySourceConfiguration()
{
var options = (IDiginsightActivitiesOptions)_optionsMonitor.CurrentValue;
return Ok(new
{
patterns = options.ActivitySources.Select(kvp => new
{
pattern = kvp.Key,
action = kvp.Value ? "include" : "exclude"
}),
totalPatterns = options.ActivitySources.Count
});
}
[HttpPost("/_diagnostics/test-pattern")]
public IActionResult TestPattern([FromBody] PatternTestRequest request)
{
var options = (IDiginsightActivitiesOptions)_optionsMonitor.CurrentValue;
var matchingPatterns = options.ActivitySources
.Where(kvp => ActivityUtils.NameMatchesPattern(request.SourceName, kvp.Key))
.Select(kvp => new
{
pattern = kvp.Key,
action = kvp.Value ? "include" : "exclude"
})
.ToList();
bool wouldListen = matchingPatterns.Any() &&
matchingPatterns.All(p => p.action == "include");
return Ok(new
{
sourceName = request.SourceName,
matchingPatterns,
decision = wouldListen ? "LISTEN" : "IGNORE"
});
}
}
public record PatternTestRequest(string SourceName);Performance Considerations
Activity Source Registration Overhead: - Pattern matching: ~1-10 microseconds per pattern per source - Performed once per ActivitySource instance (cached in .NET runtime) - Negligible impact for typical configurations (<50 patterns)
Optimization Strategies:
// Strategy 1: Use specific patterns over broad catch-alls
// β Good: Specific patterns (fast matching)
options.ActivitySources["MyApp.Services.*"] = true;
options.ActivitySources["MyApp.Controllers.*"] = true;
// β Avoid: Catch-all with many exclusions (slower)
options.ActivitySources["*"] = true;
options.ActivitySources["System.*"] = false;
options.ActivitySources["Microsoft.*"] = false;
// ... 20 more exclusions ...
// Strategy 2: Minimize pattern count
// β Good: 5-20 patterns total
// β Acceptable: 20-50 patterns
// β Problematic: >50 patterns (consider refactoring)
// Strategy 3: Use simple patterns when possible
// β Good: Exact match (fastest)
options.ActivitySources["MyApp.Services"] = true;
// β Good: Single wildcard (fast)
options.ActivitySources["MyApp.*"] = true;
options.ActivitySources["*.Controllers"] = true;
// β Acceptable: Infix wildcard (slightly slower)
options.ActivitySources["MyApp.*.Service"] = true;Monitoring Recommendations: - Monitor number of active ActivitySource instances - Track ShouldListenTo call frequency (should be low after startup) - Alert on unusual activity source creation patterns - Audit pattern configuration complexity periodically
π Reference
Interface Definition
Namespace: Diginsight.Diagnostics
Assembly: Diginsight.Diagnostics.dll
Properties
| Property | Type | Description | Default |
|---|---|---|---|
ActivitySources |
IReadOnlyDictionary<string, bool> |
Dictionary mapping activity source name patterns to boolean include/exclude values. Keys are patterns (supporting wildcards), values are true for include or false for explicit exclusion. Empty dictionary means no sources are listened to by default. |
Empty dictionary |
Implementing Classes
DiginsightActivitiesOptions: Primary concrete implementation- Implements
IDiginsightActivitiesOptions - Also implements
IDiginsightActivitiesLogOptions,IMetricRecordingOptions - Supports
IDynamicallyConfigurableandIVolatilelyConfigurable - Provides mutable
IDictionary<string, bool> ActivitySourcesproperty IDiginsightActivitiesOptions.ActivitySourcesreturns read-only view- Located in
Diginsight.Diagnosticsnamespace
- Implements
Consumption Points: - ActivityLifecycleLogEmitterRegistration: Uses this interface to determine which activity sources to log - SpanDurationMetricRecorderRegistration: Uses this interface to determine which activity sources to measure - Custom IActivityListenerRegistration implementations: Can use this interface for consistent filtering
π‘ Best Practices
Pattern Design
Organize Patterns Hierarchically:
// β Recommended: Hierarchical organization
services.Configure<DiginsightActivitiesOptions>(options =>
{
// Level 1: Application root
options.ActivitySources["MyCompany.MyApp.*"] = true;
// Level 2: Major subsystems
options.ActivitySources["MyCompany.MyApp.Api.*"] = true;
options.ActivitySources["MyCompany.MyApp.Services.*"] = true;
options.ActivitySources["MyCompany.MyApp.Data.*"] = true;
// Level 3: Specific exclusions
options.ActivitySources["MyCompany.MyApp.*.Cache"] = false;
options.ActivitySources["MyCompany.MyApp.*.Internal.*"] = false;
});
// β Avoid: Flat, unstructured patterns
services.Configure<DiginsightActivitiesOptions>(options =>
{
options.ActivitySources["UserService"] = true;
options.ActivitySources["ProductService"] = true;
options.ActivitySources["OrderService"] = true;
// ... 50 more individual service names ...
// Better: options.ActivitySources["*Service"] = true;
});Guidelines: - Use namespace-based hierarchy for activity source names - Group patterns by architectural layer or functional area - Prefer broader patterns with specific exclusions over many specific inclusions - Document the rationale for each pattern in code comments - Review patterns during code reviews for consistency
Performance Optimization
Minimize Pattern Count:
// β Recommended: Concise patterns (5-20 total)
services.Configure<DiginsightActivitiesOptions>(options =>
{
options.ActivitySources["MyApp.*"] = true;
options.ActivitySources["System.Net.Http"] = true;
options.ActivitySources["Npgsql"] = true;
options.ActivitySources["MyApp.HighFrequency.*"] = false;
});
// β Avoid: Excessive patterns (>50 total)
services.Configure<DiginsightActivitiesOptions>(options =>
{
// 50+ individual patterns for each service/controller/repository
options.ActivitySources["MyApp.Services.UserService"] = true;
options.ActivitySources["MyApp.Services.ProductService"] = true;
// ... many more ...
// Better: options.ActivitySources["MyApp.Services.*"] = true;
});Use Simple Patterns: - Exact match > prefix/suffix wildcard > infix wildcard - Fewer patterns = faster ShouldListenTo evaluation - Pattern complexity has minimal impact, but count matters
Environment-Specific Optimization:
public static IServiceCollection ConfigureOptimalActivitySources(
this IServiceCollection services,
IWebHostEnvironment environment)
{
return services.Configure<DiginsightActivitiesOptions>(options =>
{
if (environment.IsDevelopment())
{
// Development: Verbose (all sources)
options.ActivitySources["*"] = true;
}
else if (environment.IsProduction())
{
// Production: Minimal (only business logic)
options.ActivitySources["MyApp.Api.*"] = true;
options.ActivitySources["MyApp.Services.*"] = true;
// Exclude high-frequency
options.ActivitySources["MyApp.*.HealthCheck"] = false;
options.ActivitySources["MyApp.*.Metrics"] = false;
}
});
}Key Recommendations: 1. Start with minimal patterns, add as needed 2. Use broader patterns instead of many specific ones 3. Exclude high-frequency sources in production 4. Monitor activity creation rate and adjust patterns 5. Profile application with activity listening enabled
Source Organization
Naming Conventions for Activity Sources:
// β Recommended: Consistent naming scheme
// Format: {Company}.{Application}.{Layer}.{Component}
public static class ActivitySources
{
public static readonly ActivitySource Api =
new("MyCompany.MyApp.Api");
public static readonly ActivitySource Services =
new("MyCompany.MyApp.Services");
public static readonly ActivitySource Data =
new("MyCompany.MyApp.Data");
public static readonly ActivitySource Infrastructure =
new("MyCompany.MyApp.Infrastructure");
}
// Configure patterns to match hierarchy
services.Configure<DiginsightActivitiesOptions>(options =>
{
options.ActivitySources["MyCompany.MyApp.*"] = true;
});Layered Architecture Pattern:
// Layer 1: Presentation
new ActivitySource("MyApp.WebApi.Controllers")
new ActivitySource("MyApp.WebApi.Middleware")
// Layer 2: Application/Services
new ActivitySource("MyApp.Services.Users")
new ActivitySource("MyApp.Services.Orders")
// Layer 3: Domain
new ActivitySource("MyApp.Domain.UserManagement")
new ActivitySource("MyApp.Domain.OrderProcessing")
// Layer 4: Infrastructure
new ActivitySource("MyApp.Infrastructure.Database")
new ActivitySource("MyApp.Infrastructure.Messaging")
// Configuration
services.Configure<DiginsightActivitiesOptions>(options =>
{
options.ActivitySources["MyApp.WebApi.*"] = true;
options.ActivitySources["MyApp.Services.*"] = true;
options.ActivitySources["MyApp.Domain.*"] = true;
// Infrastructure excluded by default (no pattern)
});When to Create New Activity Sources: - One per major subsystem or architectural layer - One per third-party integration (if you control the source) - Separate sources for different functional areas - Donβt create too many (aim for 5-20 sources per application)
π Appendices
Appendix A: Pattern Matching Algorithm
The pattern matching algorithm (ActivityUtils.NameMatchesPattern) uses the following logic:
public static bool NameMatchesPattern(string name, string pattern)
{
string[] segments = pattern.Split('*', 3);
return segments.Length switch
{
// No wildcards: Exact match
1 => string.Equals(name, pattern, StringComparison.OrdinalIgnoreCase),
// One wildcard: Prefix or suffix match
2 => (segments[0], segments[1]) switch
{
("", "") => true, // Pattern is "*" β match all
("", suffix) => name.EndsWith(suffix, StringComparison.OrdinalIgnoreCase),
(prefix, "") => name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase),
(prefix, suffix) =>
name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) &&
name.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)
},
// Multiple wildcards: Invalid
_ => throw new ArgumentException("Invalid activity name pattern")
};
}Pattern Examples with Algorithm Trace:
// Example 1: Exact match
NameMatchesPattern("MyApp.Services", "MyApp.Services")
// segments = ["MyApp.Services"]
// Length = 1 β Exact comparison
// Result: true (case-insensitive)
// Example 2: Prefix wildcard
NameMatchesPattern("MyApp.Services.UserService", "MyApp.*")
// segments = ["MyApp", ""]
// Length = 2, case (prefix, "")
// Check: name.StartsWith("MyApp")
// Result: true
// Example 3: Suffix wildcard
NameMatchesPattern("WebApi.Controllers", "*.Controllers")
// segments = ["", "Controllers"]
// Length = 2, case ("", suffix)
// Check: name.EndsWith("Controllers")
// Result: true
// Example 4: Infix wildcard
NameMatchesPattern("MyApp.Services.UserService", "MyApp.*.UserService")
// segments = ["MyApp", ".UserService"]
// Length = 2, case (prefix, suffix)
// Check: name.StartsWith("MyApp") && name.EndsWith(".UserService")
// Result: true
// Example 5: Match all
NameMatchesPattern("AnyName", "*")
// segments = ["", ""]
// Length = 2, case ("", "")
// Result: true (always matches)
// Example 6: Invalid pattern
NameMatchesPattern("Test", "A*B*C")
// segments = ["A", "B", "C"]
// Length = 3
// Throws ArgumentException: "Invalid activity name pattern"Performance Characteristics: - Time complexity: O(n) where n is pattern length (string operations) - Space complexity: O(1) (no allocations for simple comparisons) - Exact match: Fastest (single string comparison) - Wildcard patterns: Slightly slower (prefix/suffix checks) - Case-insensitive: Uses optimized StringComparison.OrdinalIgnoreCase
Appendix B: Integration with IActivityListenerRegistration
The IDiginsightActivitiesOptions integrates with the activity listener registration system:
// 1. Application startup
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Configure activity sources
builder.Services.Configure<DiginsightActivitiesOptions>(options =>
{
options.ActivitySources["MyApp.*"] = true;
});
// Register Diginsight diagnostics
builder.Services.AddDiginsightDiagnostics();
// This registers IActivityListenerRegistration implementations
var app = builder.Build();
app.Run();
}
// 2. IActivityListenerRegistration implementation
public class ActivityLifecycleLogEmitterRegistration : IActivityListenerRegistration
{
private readonly IDiginsightActivitiesOptions activitiesOptions;
public IActivityListenerLogic Logic { get; }
public ActivityLifecycleLogEmitterRegistration(
ActivityLifecycleLogEmitter emitter,
IOptions<DiginsightActivitiesOptions> activitiesOptions)
{
Logic = emitter;
// Freeze options for thread safety
this.activitiesOptions = activitiesOptions.Value.Freeze();
}
public bool ShouldListenTo(ActivitySource activitySource)
{
string activitySourceName = activitySource.Name;
// Find all matching patterns
IEnumerable<bool> matches = activitiesOptions.ActivitySources
.Where(kvp => ActivityUtils.NameMatchesPattern(activitySourceName, kvp.Key))
.Select(kvp => kvp.Value)
.ToArray();
// Listen only if at least one matches and all are true
return matches.Any() && matches.All(value => value);
}
}
// 3. ActivitySource creation (in application code)
public class UserService
{
private static readonly ActivitySource Source = new("MyApp.Services.UserService");
public async Task<User> GetUser(int userId)
{
// Activity creation triggers listener evaluation
using var activity = Source.StartActivity("GetUser");
// ...
}
}
// 4. .NET Activity System flow
// When Source.StartActivity is called:
// a. .NET runtime checks if any ActivityListener is interested
// b. For each registered ActivityListener:
// - Calls listener.ShouldListenTo(Source)
// - If true, calls listener.Sample (if configured)
// c. Diginsight's ActivityListener uses IActivityListenerRegistration
// d. ActivityLifecycleLogEmitterRegistration.ShouldListenTo is called
// e. Pattern matching against IDiginsightActivitiesOptions.ActivitySources
// f. If matched, activity is created and loggedIntegration Flow Diagram:
Application Startup
β
Configure DiginsightActivitiesOptions
ββ ActivitySources["MyApp.*"] = true
ββ ActivitySources["System.*"] = false
β
AddDiginsightDiagnostics()
ββ Register ActivityLifecycleLogEmitterRegistration
ββ Register SpanDurationMetricRecorderRegistration
ββ Create ActivityListener with ShouldListenTo callback
β
Runtime: Create ActivitySource
var source = new ActivitySource("MyApp.Services")
β
Runtime: Start Activity
source.StartActivity("GetUser")
β
.NET Runtime: Check Listeners
foreach (listener in ActivityListener.AllListeners)
β
Call listener.ShouldListenTo(source)
β
Diginsight: IActivityListenerRegistration.ShouldListenTo
β
Query IDiginsightActivitiesOptions.ActivitySources
β
Pattern Match: ActivityUtils.NameMatchesPattern("MyApp.Services", "MyApp.*")
Result: true
β
All matches are true?
Result: true (listen to this source)
β
Activity Created and Logged
Thread Safety: - IDiginsightActivitiesOptions is read-only interface - Implementation (DiginsightActivitiesOptions) can be frozen - Freeze() creates immutable copy for use in listeners - Safe to use across threads without locking - Dynamic updates require new registration instances
End of Reference Documentation